/**
 * pam_quiz.cpp
 * LoginSecurity
 *
 * Created by Ryan Latta on 2008-12-03.
 * Copyright 2008 Ryan Latta. All rights reserved.
**/ 
#ifndef PAM_QUIZ_CPP_FFCGPEPR
#define PAM_QUIZ_CPP_FFCGPEPR

#include "Security.h"
#include <syslog.h>

#endif /* end of include guard: PAM_QUIZ_CPP_FFCGPEPR */

#define PAM_SM_ACCOUNT /** This define tells the PAM system that this is an account module */
#include <security/pam_modules.h>

//#define MODULE_NAME pam_quiz
/*
  TODO handle arguments from pam.conf files
  TODO Make sure that when its done you set that this module succeeded or failed for other modules
  TODO Make PAM Compliant
  TODO test test test
*/

void setMessage(int type, const char *, pam_message *);
int converse (pam_handle_t *pamh, int args, struct pam_message **message, struct pam_response **repsonse);

/**
 * This was all taken from pam_stress.
 * Essentially this is a function to handle the details of using pam_conv.
 * It will check for successes and errors and return them back as well.
 */
int converse (pam_handle_t *pamh, int args, struct pam_message **message, struct pam_response **response) {
	int retval;
	const void *void_conv = NULL;
	const struct pam_conv *conv = NULL;

	retval = pam_get_item(pamh, PAM_CONV, &void_conv);
	conv = (struct pam_conv*)void_conv;

	if(retval == PAM_SUCCESS && conv) {
		retval = conv->conv(args, (const struct pam_message **) message, response, conv->appdata_ptr);
		if(retval != PAM_SUCCESS) {
		syslog(LOG_ERR, "pam_quiz: converse returned %d: %s", retval, pam_strerror(pamh, retval));
		}
	}
	else {
    syslog(LOG_ERR, "pam_quiz: converse failed to get pam conv function");
		if(retval == PAM_SUCCESS) {
			retval = PAM_BAD_ITEM; /* conv was null */
		}		
	}
	 /**
	  * setMessage simply does the job of setting up the message structure.
	  * Just here for convenience.
	 **/
}
void setMessage(int type, const char *msg, pam_message *message) {
  message->msg_style = type;
  message->msg = msg;
}
/**
 * pam_sm_acct_mgmt is the method PAM will use to do account authorization.
 * The purpose of this method is to simply ask questions to the user randomly
 * and assess if they are human or not.  What this boils down to is if they can
 * get x out of y questions correct, they will be authorized.
**/
PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argvc) {
  int retval, rv;
  string input;
  struct pam_message *message;
  struct pam_response *response;
  float totalQuestions = 5;
  float correctAnswers = 0;
  float threshold = 0.7;
  QuestionFactory *qf = new QuestionFactory();
  Question *q = qf->getQuestion();
  
  response = NULL;
  //rv = PAM_AUTH_ERR; // Just by default.
  
  /**
    Order of Events:
    -1) Get a conversation function pointer to the application (Hopefully).
    -2) Generate question based on random numbers and flags (Future).
    -3) Build the conversation functions' message structure and response structure.
      -Note: Send only one message at a time. (Compatability)
    -4) Initiate the conversation.
    -5) Check the response structure for the correct answer.
    -6) Repeat if necessary (Future).
    -7) Free the response strucutre
    -8) Return our success or failure.
  */
  syslog(LOG_ERR, "pam_quiz: starting...");

  for(int i = 0; i < totalQuestions; i++) {
    syslog(LOG_ERR, "pam_quiz: generating question...");
    q->makeQuestion();
    
    setMessage(PAM_PROMPT_ECHO_ON, q->getQuestion().c_str(), message);
    retval = converse(pamh, 1, &message, &response);

    if(retval != PAM_SUCCESS) {
      //Shit, another error, bail.
      syslog(LOG_ERR, "pam_quiz: couldn't converse!");
      rv = PAM_AUTH_ERR;
    }
    if(q->getAnswer() == (response->resp)) {
        correctAnswers++;
        syslog(LOG_ERR, "pam_quiz: correct answer received - %d", correctAnswers);
    }
    else {
      //syslog(LOG_ERR, "pam_quiz: INCORRECT answer received");
    }
    response = NULL;
  }

  if((float)correctAnswers/totalQuestions < threshold) {
    syslog(LOG_ERR, "pam_quiz: informing the user of too many failed questions.");
    setMessage(PAM_TEXT_INFO, "You missed too many quesitons.", message);
    retval = converse(pamh, 1, &message, &response);
    rv = PAM_PERM_DENIED;
  }
  else {
    //Set success information using pam_set_item
    syslog(LOG_ERR, "pam_quiz: authorization granted!");
    setMessage(PAM_TEXT_INFO, "Acceptable.", message);
    retval = converse(pamh, 1, &message, &response);
    rv = PAM_SUCCESS;
  }
  
  free(response);
  delete qf;
  
  return rv;
}
/*
 * 

#ifdef PAM_STATIC
struct pam_module _pam_quiz_modstrut = {
  "pam_quiz",
  NULL,
  NULL,
  pam_sm_acct_mgmt,
  NULL,
  NULL,
  NULL
};

#endif
*/
